
Adélia Cruz
Neural Network Developer
TL;DR: Crawler Crawlee sering kali menghadapi penghalang CAPTCHA. Mengintegrasikan CapSolver memungkinkan Anda menyelesaikan reCAPTCHA, Turnstile, dan lainnya, sehingga alur kerja scraping tetap stabil dan otomatis.

Saat membangun crawler dengan Crawlee, menghadapi CAPTCHA hampir tidak terhindarkan—terutama di situs modern dengan perlindungan bot yang agresif. Bahkan crawler Playwright atau HTTP yang sudah dikonfigurasi dengan baik bisa terblokir saat tantangan reCAPTCHA, Turnstile, atau serupa muncul.
Panduan ini fokus pada pendekatan praktis: menggunakan CapSolver untuk menyelesaikan tantangan CAPTCHA secara langsung dalam alur kerja Crawlee. Alih-alih terus-menerus melawan fingerprint browser, Anda akan melihat cara mendeteksi jenis CAPTCHA umum, menyelesaikannya secara programatik, dan menjaga crawler Anda berjalan secara andal dalam skenario scraping dunia nyata.
Crawlee adalah perpustakaan scraping web dan otomasi browser untuk Node.js yang dirancang untuk membangun crawler yang andal dan terlihat seperti manusia, serta melewati radar perlindungan bot modern. Dibangun dengan TypeScript, perpustakaan ini menyediakan antarmuka yang sederhana namun fleksibel untuk penggunaan tingkat tinggi dan tingkat rendah.
Crawlee menawarkan berbagai tipe crawler untuk berbagai kebutuhan:
| Tipe Crawler | Deskripsi |
|---|---|
| CheerioCrawler | Crawler HTTP ultra-cepat menggunakan Cheerio untuk parsing HTML |
| PlaywrightCrawler | Otomasi browser lengkap dengan Playwright untuk situs yang berbasis JavaScript |
| PuppeteerCrawler | Otomasi browser lengkap dengan Puppeteer untuk rendering JavaScript |
| JSDOMCrawler | Crawler HTTP dengan JSDOM untuk eksekusi JavaScript tanpa browser |
CapSolver adalah layanan penyelesaian CAPTCHA yang terkemuka yang menyediakan solusi berbasis AI untuk melewati berbagai tantangan CAPTCHA. Dengan dukungan untuk berbagai jenis CAPTCHA dan waktu respons yang cepat, CapSolver dapat terintegrasi secara mulus ke dalam alur kerja otomatis.
Saat membangun crawler Crawlee yang berinteraksi dengan situs yang dilindungi, tantangan CAPTCHA bisa menghentikan seluruh alur scraping Anda. Berikut alasan mengapa integrasi ini penting:
Pertama, instal paket yang diperlukan:
npm install crawlee playwright axios
Atau dengan yarn:
yarn add crawlee playwright axios
Berikut adalah kelas utilitas CapSolver yang dapat digunakan kembali dan bisa diterapkan di berbagai proyek Crawlee Anda:
import axios from 'axios';
const CAPSOLVER_API_KEY = 'API_KEY_CAPSOLVER_ANDA';
interface TaskResult {
status: string;
solution?: {
gRecaptchaResponse?: string;
token?: string;
};
errorDescription?: string;
}
class CapSolverService {
private apiKey: string;
private baseUrl = 'https://api.capsolver.com';
constructor(apiKey: string = CAPSOLVER_API_KEY) {
this.apiKey = apiKey;
}
async createTask(taskData: object): Promise<string> {
const response = await axios.post(`${this.baseUrl}/createTask`, {
clientKey: this.apiKey,
task: taskData
});
if (response.data.errorId !== 0) {
throw new Error(`Kesalahan CapSolver: ${response.data.errorDescription}`);
}
return response.data.taskId;
}
async getTaskResult(taskId: string, maxAttempts = 60): Promise<TaskResult> {
for (let i = 0; i < maxAttempts; i++) {
await this.sleep(2000);
const response = await axios.post(`${this.baseUrl}/getTaskResult`, {
clientKey: this.apiKey,
taskId
});
if (response.data.status === 'ready') {
return response.data;
}
if (response.data.status === 'failed') {
throw new Error(`Tugas gagal: ${response.data.errorDescription}`);
}
}
throw new Error('Waktu tunggu solusi CAPTCHA habis');
}
private sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
async solveReCaptchaV2(websiteUrl: string, websiteKey: string): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV2TaskProxyLess',
websiteURL: websiteUrl,
websiteKey
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse || '';
}
async solveReCaptchaV3(
websiteUrl: string,
websiteKey: string,
pageAction = 'submit'
): Promise<string> {
const taskId = await this.createTask({
type: 'ReCaptchaV3TaskProxyLess',
websiteURL: websiteUrl,
websiteKey,
pageAction
});
const result = await this.getTaskResult(taskId);
return result.solution?.gRecaptchaResponse || '';
}
async solveTurnstile(websiteUrl: string, websiteKey: string): Promise<string> {
const taskId = await this.createTask({
type: 'AntiTurnstileTaskProxyLess',
websiteURL: websiteUrl,
websiteKey
});
const result = await this.getTaskResult(taskId);
return result.solution?.token || '';
}
}
export const capSolver = new CapSolverService();
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const RECAPTCHA_SITE_KEY = 'KUNCI_SITUS_ANDA';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Memproses ${request.url}`);
// Periksa apakah halaman memiliki reCAPTCHA
const hasRecaptcha = await page.$('.g-recaptcha');
if (hasRecaptcha) {
log.info('reCAPTCHA terdeteksi, menyelesaikan...');
// Dapatkan kunci situs dari halaman
const siteKey = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
) || RECAPTCHA_SITE_KEY;
// Selesaikan CAPTCHA
const token = await capSolver.solveReCaptchaV2(request.url, siteKey);
// Sisipkan token - textarea tersembunyi, jadi kita gunakan JavaScript
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Kirim formulir
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('reCAPTCHA berhasil diselesaikan!');
}
// Ekstrak data setelah CAPTCHA diselesaikan
const title = await page.title();
const content = await page.locator('body').innerText();
await Dataset.pushData({
title,
content: content.slice(0, 1000)
});
},
maxRequestsPerCrawl: 50,
headless: true
});
await crawler.run(['https://example.com/halaman-terlindungi']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Memproses ${request.url}`);
// reCAPTCHA v3 tidak terlihat, deteksi melalui skrip
const recaptchaScript = await page.$('script[src*="recaptcha/api.js?render="]');
if (recaptchaScript) {
log.info('reCAPTCHA v3 terdeteksi, menyelesaikan...');
// Ekstrak kunci situs dari src skrip
const scriptSrc = await recaptchaScript.getAttribute('src') || '';
const siteKeyMatch = scriptSrc.match(/render=([^&]+)/);
const siteKey = siteKeyMatch ? siteKeyMatch[1] : '';
if (siteKey) {
// Selesaikan reCAPTCHA v3
const token = await capSolver.solveReCaptchaV3(
request.url,
siteKey,
'submit'
);
// Sisipkan token ke input tersembunyi menggunakan JavaScript
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
log.info('Token reCAPTCHA v3 berhasil disisipkan!');
}
}
// Lanjutkan dengan pengiriman formulir atau ekstraksi data
const title = await page.title();
const url = page.url();
await Dataset.pushData({ title, url });
}
});
await crawler.run(['https://example.com/v3-terlindungi']);
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log }) {
log.info(`Memproses ${request.url}`);
// Periksa widget Turnstile
const hasTurnstile = await page.$('.cf-turnstile');
if (hasTurnstile) {
log.info('Cloudflare Turnstile terdeteksi, menyelesaikan...');
// Dapatkan kunci situs
const siteKey = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
// Selesaikan Turnstile
const token = await capSolver.solveTurnstile(request.url, siteKey);
// Sisipkan token menggunakan JavaScript (input tersembunyi)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Kirim formulir
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
log.info('Turnstile berhasil diselesaikan!');
}
}
// Ekstrak data
const title = await page.title();
const content = await page.locator('body').innerText();
await Dataset.pushData({
title,
content: content.slice(0, 500)
});
}
});
await crawler.run(['https://example.com/terlindungi-turnstile']);
Berikut adalah crawler lanjutan yang secara otomatis mendeteksi dan menyelesaikan berbagai jenis CAPTCHA:
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
interface CaptchaInfo {
type: 'recaptcha-v2' | 'recaptcha-v3' | 'turnstile' | 'none';
siteKey: string | null;
}
async function detectCaptcha(page: any): Promise<CaptchaInfo> {
// Periksa reCAPTCHA v2
const recaptchaV2 = await page.$('.g-recaptcha');
if (recaptchaV2) {
const siteKey = await page.$eval('.g-recaptcha', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { type: 'recaptcha-v2', siteKey };
}
// Periksa reCAPTCHA v3
const recaptchaV3Script = await page.$('script[src*="recaptcha/api.js?render="]');
if (recaptchaV3Script) {
const scriptSrc = await recaptchaV3Script.getAttribute('src') || '';
const match = scriptSrc.match(/render=([^&]+)/);
const siteKey = match ? match[1] : null;
return { type: 'recaptcha-v3', siteKey };
}
// Periksa Turnstile
const turnstile = await page.$('.cf-turnstile');
if (turnstile) {
const siteKey = await page.$eval('.cf-turnstile', (el: Element) =>
el.getAttribute('data-sitekey')
);
return { type: 'turnstile', siteKey };
}
return { type: 'none', siteKey: null };
}
async function solveCaptcha(
page: any,
url: string,
captchaInfo: CaptchaInfo
): Promise<void> {
if (!captchaInfo.siteKey || captchaInfo.type === 'none') return;
let token: string;
switch (captchaInfo.type) {
case 'recaptcha-v2':
token = await capSolver.solveReCaptchaV2(url, captchaInfo.siteKey);
// Textarea tersembunyi - gunakan JavaScript untuk mengatur nilai
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
}, token);
break;
case 'recaptcha-v3':
token = await capSolver.solveReCaptchaV3(url, captchaInfo.siteKey);
// Input tersembunyi - gunakan JavaScript untuk mengatur nilai
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
case 'turnstile':
token = await capSolver.solveTurnstile(url, captchaInfo.siteKey);
// Input tersembunyi - gunakan JavaScript untuk mengatur nilai
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
break;
}
}
const crawler = new PlaywrightCrawler({
async requestHandler({ page, request, log, enqueueLinks }) {
log.info(`Memproses ${request.url}`);
// Deteksi CAPTCHA otomatis
const captchaInfo = await detectCaptcha(page);
if (captchaInfo.type !== 'none') {
log.info(`Deteksi ${captchaInfo.type}, menyelesaikan...`);
await solveCaptcha(page, request.url, captchaInfo);
// Kirim formulir jika ada
const submitBtn = await page.$('button[type="submit"], input[type="submit"]');
if (submitBtn) {
await submitBtn.click();
await page.waitForLoadState('networkidle');
}
log.info('CAPTCHA berhasil diselesaikan!');
}
// Ekstrak data
const title = await page.title();
const url = page.url();
const text = await page.locator('body').innerText();
await Dataset.pushData({
title,
url,
text: text.slice(0, 1000)
});
// Lanjutkan pencrawlan
await enqueueLinks();
},
maxRequestsPerCrawl: 100
});
await crawler.run(['https://example.com']);
Setiap jenis CAPTCHA memerlukan metode pengiriman yang berbeda dalam konteks browser:
async function submitRecaptchaToken(page: any, token: string): Promise<void> {
// Textarea respons tersembunyi - gunakan JavaScript untuk mengatur nilai
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, token: string) => {
el.style.display = 'block';
el.value = token;
}, token);
// Juga atur input tersembunyi jika ada (umum dalam implementasi kustom)
try {
await page.$eval('input[name="g-recaptcha-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
} catch (e) {
// Input mungkin tidak ada
}
// Kirim formulir
await page.click('form button[type="submit"]');
}
async function submitTurnstileToken(page: any, token: string): Promise<void> {
// Atur token di input tersembunyi menggunakan JavaScript
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, token: string) => {
el.value = token;
}, token);
// Kirim formulir
await page.click('form button[type="submit"]');
}
Untuk skenario di mana Anda ingin penyelesaian CAPTCHA otomatis, Anda dapat memuat ekstensi CapSolver:
import { PlaywrightCrawler } from 'crawlee';
import path from 'path';
const crawler = new PlaywrightCrawler({
launchContext: {
launchOptions: {
// Muat ekstensi CapSolver
args: [
`--disable-extensions-except=${path.resolve('./capsolver-extension')}`,
`--load-extension=${path.resolve('./capsolver-extension')}`
],
headless: false // Ekstensi memerlukan mode yang tidak headless
}
},
async requestHandler({ page, request, log }) {
log.info(`Memproses ${request.url}`);
// Ekstensi akan secara otomatis menyelesaikan CAPTCHA
// Tunggu CAPTCHA yang mungkin diselesaikan
await page.waitForTimeout(5000);
// Lanjutkan dengan scraping
const title = await page.title();
const content = await page.locator('body').innerText();
console.log({ title, content });
}
});
await crawler.run(['https://example.com/captcha-page']);
async function solveWithRetry(
solverFn: () => Promise<string>,
maxRetries = 3
): Promise<string> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
try {
return await solverFn();
} catch (error) {
if (attempt === maxRetries - 1) throw error;
const delay = Math.pow(2, attempt) * 1000; // Backoff eksponensial
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw new Error('Jumlah pengulangan maksimum tercapai');
}
// Penggunaan
const token = await solveWithRetry(() =>
capSolver.solveReCaptchaV2(url, siteKey)
);
import axios from 'axios';
async function checkBalance(apiKey: string): Promise<number> {
const response = await axios.post('https://api.capsolver.com/getBalance', {
clientKey: apiKey
});
return response.data.balance || 0;
}
// Periksa sebelum memulai crawler
const balance = await checkBalance(CAPSOLVER_API_KEY);
if (balance < 1) {
console.warn('Saldo CapSolver rendah! Silakan isi ulang.');
}
import { PlaywrightCrawler, Dataset } from 'crawlee';
import { capSolver } from './capsolver-service';
// Simpan token yang telah diselesaikan untuk kombinasi domain/kunci yang sama
const tokenCache = new Map<string, { token: string; timestamp: number }>();
const TOKEN_TTL = 90000; // 90 detik
async function getCachedToken(
url: string,
siteKey: string,
solverFn: () => Promise<string>
): Promise<string> {
const cacheKey = `${new URL(url).hostname}:${siteKey}`;
const cached = tokenCache.get(cacheKey);
if (cached && Date.now() - cached.timestamp < TOKEN_TTL) {
return cached.token;
}
const token = await solverFn();
tokenCache.set(cacheKey, { token, timestamp: Date.now() });
return token;
}
import { PlaywrightCrawler, ProxyConfiguration } from 'crawlee';
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: [
'http://user:pass@proxy1.example.com:8080',
'http://user:pass@proxy2.example.com:8080',
'http://user:pass@proxy3.example.com:8080'
]
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
async requestHandler({ page, request, log, proxyInfo }) {
log.info(`Menggunakan proxy: ${proxyInfo?.url}`);
// Logika penyelesaian CAPTCHA dan scraping Anda di sini
}
});
import { PlaywrightCrawler, Dataset, ProxyConfiguration } from 'crawlee';
import { capSolver } from './capsolver-service';
interface Product {
name: string;
price: string;
url: string;
image: string;
}
const proxyConfiguration = new ProxyConfiguration({
proxyUrls: ['http://user:pass@proxy.example.com:8080']
});
const crawler = new PlaywrightCrawler({
proxyConfiguration,
maxRequestsPerCrawl: 200,
maxConcurrency: 5,
async requestHandler({ page, request, log, enqueueLinks }) {
log.info(`Menjelajah: ${request.url}`);
// Periksa apakah ada CAPTCHA
const hasRecaptcha = await page.$('.g-recaptcha');
const hasTurnstile = await page.$('.cf-turnstile');
if (hasRecaptcha) {
const siteKey = await page.$eval(
'.g-recaptcha',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Menyelesaikan reCAPTCHA...');
const token = await capSolver.solveReCaptchaV2(request.url, siteKey);
// Injeksikan token menggunakan JavaScript (elemen tersembunyi)
await page.$eval('#g-recaptcha-response', (el: HTMLTextAreaElement, t: string) => {
el.style.display = 'block';
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
if (hasTurnstile) {
const siteKey = await page.$eval(
'.cf-turnstile',
(el) => el.getAttribute('data-sitekey')
);
if (siteKey) {
log.info('Menyelesaikan Turnstile...');
const token = await capSolver.solveTurnstile(request.url, siteKey);
// Injeksikan token menggunakan JavaScript (elemen tersembunyi)
await page.$eval('input[name="cf-turnstile-response"]', (el: HTMLInputElement, t: string) => {
el.value = t;
}, token);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
}
}
// Ekstrak data produk menggunakan lokator Playwright
const productCards = await page.locator('.product-card').all();
const products: Product[] = [];
for (const card of productCards) {
products.push({
name: await card.locator('.product-name').innerText().catch(() => ''),
price: await card.locator('.product-price').innerText().catch(() => ''),
url: await card.locator('a').getAttribute('href') || '',
image: await card.locator('img').getAttribute('src') || ''
});
}
if (products.length > 0) {
await Dataset.pushData(products);
log.info(`Ekstrak ${products.length} produk`);
}
// Enqueue tautan pagination dan kategori
await enqueueLinks({
globs: ['**/products/**', '**/page/**', '**/category/**']
});
},
failedRequestHandler({ request, log }) {
log.error(`Permintaan gagal: ${request.url}`);
}
});
// Mulai pencrawlan
await crawler.run(['https://example-store.com/products']);
// Ekspor hasil
const dataset = await Dataset.open();
await dataset.exportToCSV('products.csv');
console.log('Penjelajahan selesai! Hasil disimpan ke products.csv');
Mengintegrasikan CapSolver dengan Crawlee membuka potensi penuh untuk pengambilan data web bagi pengembang Node.js. Dengan menggabungkan infrastruktur pencrawlan yang kuat dari Crawlee dengan kemampuan penyelesaian CAPTCHA terkemuka dari CapSolver, Anda dapat membangun penjelajah yang andal yang mampu menangani mekanisme perlindungan bot paling menantang.
Baik Anda membangun pipeline ekstraksi data, sistem pemantauan harga, atau alat agregasi konten, kombinasi Crawlee + CapSolver memberikan keandalan dan skalabilitas yang dibutuhkan untuk lingkungan produksi.
Siap memulai? Daftar untuk CapSolver dan gunakan kode bonus CRAWLEE untuk bonus tambahan 6% pada setiap pengisian ulang!
Crawlee adalah perpustakaan pengambilan data dan otomasi browser untuk Node.js yang dirancang untuk membangun pencrawlan yang andal. Ini mendukung pencrawlan berbasis HTTP (dengan Cheerio/JSDOM) dan otomasi browser penuh (dengan Playwright/Puppeteer), serta memiliki fitur bawaan seperti rotasi proxy, manajemen sesi, dan keamanan anti-bot.
CapSolver berintegrasi dengan Crawlee melalui kelas layanan yang mengemas API CapSolver. Dalam handler permintaan crawler Anda, Anda dapat mendeteksi tantangan CAPTCHA dan menggunakan CapSolver untuk menyelesaikannya, lalu menyisipkan token kembali ke halaman.
CapSolver mendukung berbagai jenis CAPTCHA seperti reCAPTCHA v2, reCAPTCHA v3, Cloudflare Turnstile, AWS WAF, GeeTest, dan banyak lainnya.
CapSolver menawarkan harga kompetitif berdasarkan jenis dan volume CAPTCHA yang diselesaikan. Kunjungi capsolver.com untuk detail harga terkini. Gunakan kode CRAWLEE untuk bonus 6% pada pengisian ulang pertama Anda.
Ya! CapSolver menyediakan API REST yang dapat diintegrasikan dengan kerangka kerja Node.js apa pun, termasuk Express, Puppeteer standalone, Selenium, dan lainnya.
Ya, Crawlee adalah open-source dan dirilis di bawah lisensi Apache 2.0. Kerangka kerja ini gratis digunakan, meskipun Anda mungkin menghadapi biaya layanan proxy dan layanan penyelesaian CAPTCHA seperti CapSolver.
Kunci CAPTCHA biasanya ditemukan dalam sumber HTML halaman. Cari:
data-sitekey pada elemen .g-recaptchadata-sitekey pada elemen .cf-turnstilePelajari arsitektur pengambilan data web Rust yang dapat diskalakan dengan reqwest, scraper, pengambilan data asinkron, pengambilan data browser tanpa tampilan, rotasi proxy, dan penanganan CAPTCHA yang sesuai aturan.

Mengotomasi penyelesaian CAPTCHA dengan Nanobot dan CapSolver. Gunakan Playwright untuk menyelesaikan reCAPTCHA dan Cloudflare secara otomatis.
